package com.im

import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.ServerSocket
import java.net.Socket
import java.sql.Connection
import java.sql.DriverManager
import java.sql.Statement
import java.util.*

import kotlin.collections.HashMap

// 服务器
class Server: ServerSocket(), Runnable {
    private lateinit var mDatabase: Connection                          // 数据库
    private val mOnlineUsers: MutableMap<String, Client> = HashMap()    // 在线用户列表

    /**
     * 构造方法。
     */
    init {
        // 创建数据库
        this.createDatabase()
    }

    /**
     * 析构方法。
     */
    protected fun finalize() {
        try {
            // 关闭套接字
            super.close()

            // 关闭数据库
            mDatabase.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }

        // 清空在线用户列表
        mOnlineUsers.clear()
    }

    /**
     * 创建数据库。
     */
    private fun createDatabase() {
        try {
            // 打开数据库
            Class.forName("org.sqlite.JDBC")
            mDatabase = DriverManager.getConnection(Const.DB_PATH)

            // 创建表
            val stat = mDatabase.createStatement()

            /* 用户表 */
            var sql = String.format("CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY,%s TEXT,%s INTEGER)",
                Const.TABLE_USERS, Const.FIELD_NAME, Const.FIELD_PASSWORD, Const.FIELD_ICON_INDEX)
            stat.executeUpdate(sql)

            /* 好友表 */
            sql = String.format("CREATE TABLE IF NOT EXISTS %s(%s TEXT,%s TEXT)", Const.TABLE_FRIENDS, Const.FIELD_OWNER, Const.FIELD_FRIEND_NAME)
            stat.executeUpdate(sql)

            /* 离线聊天表 */
            sql = String.format("CREATE TABLE IF NOT EXISTS %s(%s TEXT,%s TEXT,%s TEXT,%s TEXT)",
                Const.TABLE_OFFLINE_CHAT, Const.FIELD_DST_USER, Const.FIELD_SRC_USER, Const.FIELD_CONTENT, Const.FIELD_TIME)
            stat.executeUpdate(sql)

            /* 离线添加好友表 */
            sql = String.format("CREATE TABLE IF NOT EXISTS %s(%s TEXT,%s TEXT,%s INTEGER)",
                Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_SRC_USER, Const.FIELD_DST_USER, Const.FIELD_RESULT)
            stat.executeUpdate(sql)

            stat.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 处理添加好友请求。
     * @param request   添加好友请求
     * @param client    客户端
     */
    private fun handleAddFriendRequest(request: AddFriendRequest, client: Client) {
        try {
            val stat = mDatabase.createStatement()
            var sql: String
            val result: AddFriendResult

            // 检查目标用户是否存在
            sql = String.format("SELECT * FROM %s WHERE %s='%s'", Const.TABLE_USERS, Const.FIELD_NAME, request.mDstUsername)
            var query = stat.executeQuery(sql)
            if (!query.next()) {
                // 查无此人，通知源用户
                result = AddFriendResult(0, request.mDstUsername, ResultCode.USER_NOT_FOUND, "")
                client.sendData(result)
                stat.close()
                return
            }
            query.close()

            // 检查是否已经是好友了
            sql = String.format("SELECT * FROM %s WHERE %s='%s' AND %s='%s'", Const.TABLE_FRIENDS,
                Const.FIELD_OWNER, request.mSrcUsername, Const.FIELD_FRIEND_NAME, request.mDstUsername)
            query = stat.executeQuery(sql)
            if (query.next()) {
                // 已经是好友了，通知源用户
                result = AddFriendResult(0, request.mDstUsername, ResultCode.ALREADY_BE_FRIENDS, "")
                client.sendData(result)
                stat.close()
                return
            }
            query.close()

            // 如果目标用户不在线，则将请求信息插入到数据库中
            val friendUser = mOnlineUsers[request.mDstUsername]
            if (friendUser == null) {
                sql = String.format("INSERT INTO %s (%s,%s) VALUES('%s','%s')", Const.TABLE_OFFLINE_ADD_FRIEND,
                    Const.FIELD_SRC_USER, Const.FIELD_DST_USER, request.mSrcUsername, request.mDstUsername)
                stat.execute(sql)
                stat.close()
                return
            }
            stat.close()

            // 向目标用户发送请求
            friendUser.sendData(request)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 处理添加好友结果。
     * @param result    添加好友结果
     */
    private fun handleAddFriendResult(result: AddFriendResult) {
        try {
            val stat = mDatabase.createStatement()
            var sql: String

            // 如果结果是同意，则将相关信息写到好友表中
            if (result.mResultCode == ResultCode.ADD_FRIEND_ALLOW) {
                sql = String.format("INSERT INTO %s VALUES('%s','%s')", Const.TABLE_FRIENDS, result.mSrcUsername, result.mDstUsername)
                stat.execute(sql)

                sql = String.format("INSERT INTO %s VALUES('%s','%s')", Const.TABLE_FRIENDS, result.mDstUsername, result.mSrcUsername)
                stat.execute(sql)
            }

            // 如果目标用户不在线，则将结果信息插入到数据库中
            val friendUser = mOnlineUsers[result.mSrcUsername]
            if (friendUser == null) {
                sql = String.format("INSERT INTO %s VALUES('%s','%s','%d')",
                    Const.TABLE_OFFLINE_ADD_FRIEND, result.mSrcUsername, result.mDstUsername, result.mResultCode.ordinal)
                stat.execute(sql)
                stat.close()
                return
            }
            stat.close()

            // 向目标用户发送结果
            friendUser.sendData(result)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 处理聊天。
     * @param chatInfo  聊天信息
     */
    private fun handleChat(chatInfo: ChatInfo) {
        try {
            // 处理群发消息
            if (chatInfo.mDstUsername == ImProtocol.MULTI_CHAT) {
                for ((key, value) in mOnlineUsers) {
                    // 跳过自己，向其它用户发送聊天信息
                    if (key != chatInfo.mSrcUsername) {
                        value.sendData(chatInfo)
                    }
                }
                return
            }

            // 如果目标用户不在线，则将聊天信息插入到数据库中
            val friendUser = mOnlineUsers[chatInfo.mDstUsername]
            if (friendUser == null) {
                val stat = mDatabase.createStatement()
                val sql = String.format("INSERT INTO %s VALUES('%s','%s','%s','%s')",
                    Const.TABLE_OFFLINE_CHAT, chatInfo.mDstUsername, chatInfo.mSrcUsername, chatInfo.mContent, chatInfo.mTime)
                stat.execute(sql)
                stat.close()
                return
            }

            // 发送给目标用户
            friendUser.sendData(chatInfo)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 处理离线请求。
     * @param request   离线请求
     */
    private fun handleLogoff(request: LogoffRequest) {
        // 将该用户从在线列表移除
        mOnlineUsers.remove(request.mUsername)

        // 广播该用户的离线请求
        for ((_, value) in mOnlineUsers) {
            value.sendData(request)
        }
    }

    /**
     * 处理登录请求。
     * @param request   登录请求
     * @param client    客户端
     */
    private fun handleLogon(request: LogonRequest, client: Client) {
        try {
            val logonResult = LogonResult()
            val stat = mDatabase.createStatement()
            var sql: String

            // 检查该用户是否已经登录
            val onlineUser = mOnlineUsers[request.mUserInfo.mUsername]
            if (onlineUser != null) {
                // 已经登录了
                stat.close()
                logonResult.mResultCode = ResultCode.USER_ALREADY_LOGON
                client.sendData(logonResult)
                return
            }

            // 从数据库中查找该用户
            sql = String.format("SELECT * FROM %s WHERE %s='%s'", Const.TABLE_USERS, Const.FIELD_NAME, request.mUserInfo.mUsername)
            var query = stat.executeQuery(sql)
            if (!query.next()) {
                // 查无此人
                query.close()
                stat.close()
                logonResult.mResultCode = ResultCode.USER_NOT_FOUND
                client.sendData(logonResult)
                return
            }

            // 比对密码
            val password = query.getString(Const.FIELD_PASSWORD)
            if (password != request.mUserInfo.mPassword) {
                // 密码不正确
                query.close()
                stat.close()
                logonResult.mResultCode = ResultCode.PASSWORD_WRONG
                client.sendData(logonResult)
                return
            }

            // 登录成功，获取该用户的相关信息
            logonResult.mResultCode = ResultCode.LOGON_SUCCEEDED
            logonResult.mIconIndex = query.getInt(Const.FIELD_ICON_INDEX)
            query.close()

            // 获取该用户的好友列表
            sql = String.format("SELECT * FROM %s WHERE %s='%s'", Const.TABLE_FRIENDS, Const.FIELD_OWNER, request.mUserInfo.mUsername)
            query = stat.executeQuery(sql)
            val friendList = LinkedList<UserInfo>()
            var friendName: String
            var iconIndex: Int
            val iconIndexStat = mDatabase.createStatement()
            var isOnline: Boolean
            val onlineList = LinkedList<Boolean>()

            var friendCount = 0
            while (query.next()) {
                friendName = query.getString(Const.FIELD_FRIEND_NAME)

                sql = String.format("SELECT %s FROM %s WHERE %s='%s'", Const.FIELD_ICON_INDEX, Const.TABLE_USERS, Const.FIELD_NAME, friendName)
                val iconIndexQuery = iconIndexStat.executeQuery(sql)
                iconIndexQuery.next()
                iconIndex = iconIndexQuery.getInt(Const.FIELD_ICON_INDEX)
                iconIndexQuery.close()

                val userInfo = UserInfo(iconIndex, "", friendName)
                friendList.add(userInfo)

                val friendUser = mOnlineUsers[friendName]
                isOnline = (friendUser != null)
                onlineList.add(isOnline)

                // 如果好友在线，则向好友发送通知
                if (isOnline) {
                    val friendLogon = FriendLogon(request.mUserInfo.mUsername)
                    friendUser!!.sendData(friendLogon)
                }
                friendCount++
            }
            query.close()
            logonResult.mFriendCount = friendCount
            logonResult.mFriends = friendList
            logonResult.mFriendsOnline = onlineList

            // 发送登录结果
            client.sendData(logonResult)

            // 查询是否有和该用户相关的离线添加好友请求，有则发送
            sql = String.format("SELECT * FROM %s WHERE %s='%s' AND %s IS NULL",
                Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_DST_USER, request.mUserInfo.mUsername, Const.FIELD_RESULT)
            query = stat.executeQuery(sql)
            var srcUsername: String
            while (query.next()) {
                srcUsername = query.getString(Const.FIELD_SRC_USER)
                sql = String.format("SELECT %s FROM %s WHERE %s='%s'", Const.FIELD_ICON_INDEX, Const.TABLE_USERS, Const.FIELD_NAME, srcUsername)
                val iconIndexQuery = iconIndexStat.executeQuery(sql)
                iconIndexQuery.next()
                val addFriendRequest = AddFriendRequest(request.mUserInfo.mUsername, iconIndexQuery.getInt(Const.FIELD_ICON_INDEX), srcUsername)
                iconIndexQuery.close()
                client.sendData(addFriendRequest)
            }
            query.close()

            // 删除数据库中的离线添加好友请求
            sql = String.format("DELETE FROM %s WHERE %s='%s'", Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_DST_USER, request.mUserInfo.mUsername)
            stat.execute(sql)

            // 查询是否有和该用户相关的离线添加好友结果，有则发送
            sql = String.format("SELECT * FROM %s WHERE %s='%s' AND %s IS NOT NULL",
                Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_SRC_USER, request.mUserInfo.mUsername, Const.FIELD_RESULT)
            query = stat.executeQuery(sql)
            var dstUsername: String
            while (query.next()) {
                dstUsername = query.getString(Const.FIELD_DST_USER)
                val result = ResultCode.entries[query.getInt(Const.FIELD_RESULT)]
                val addFriendResult = AddFriendResult(-1, dstUsername, result, request.mUserInfo.mUsername)
                client.sendData(addFriendResult)
            }
            query.close()

            // 删除数据库中的离线添加好友结果
            sql = String.format("DELETE FROM %s WHERE %s='%s'",
                Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_SRC_USER, request.mUserInfo.mUsername)
            stat.execute(sql)

            // 查询是否有和该用户相关的离线聊天消息，有则发送
            sql = String.format("SELECT * FROM %s WHERE %s='%s'", Const.TABLE_OFFLINE_CHAT, Const.FIELD_DST_USER, request.mUserInfo.mUsername)
            query = stat.executeQuery(sql)
            while (query.next()) {
                srcUsername = query.getString(Const.FIELD_SRC_USER)
                sql = String.format("SELECT %s FROM %s WHERE %s='%s'",
                    Const.FIELD_ICON_INDEX, Const.TABLE_USERS, Const.FIELD_NAME, srcUsername)
                val iconIndexQuery = iconIndexStat.executeQuery(sql)
                iconIndexQuery.next()
                val srcIconIndex = iconIndexQuery.getInt(Const.FIELD_ICON_INDEX)
                iconIndexQuery.close()
                val content = query.getString(Const.FIELD_CONTENT)
                val time = query.getString(Const.FIELD_TIME)
                val chatInfo = ChatInfo(content, request.mUserInfo.mUsername, srcIconIndex, srcUsername, time)
                client.sendData(chatInfo)
            }
            iconIndexStat.close()
            query.close()

            // 删除数据库中的离线聊天信息
            sql = String.format("DELETE FROM %s WHERE %s='%s'", Const.TABLE_OFFLINE_CHAT, Const.FIELD_DST_USER, request.mUserInfo.mUsername)
            stat.execute(sql)
            stat.close()

            // 将该用户加入在线列表
            mOnlineUsers[request.mUserInfo.mUsername] = client
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    /**
     * 处理注册请求。
     * @param request   注册请求
     * @param client    客户端
     */
    private fun handleReg(request: RegRequest, client: Client) {
        val regResult = RegResult()
        var stat: Statement? = null
        try {
            stat = mDatabase.createStatement()

            // 将该用户信息插入到数据库中
            val sql = String.format("INSERT INTO %s VALUES('%s','%s','%d')", Const.TABLE_USERS,
                request.mUserInfo.mUsername, request.mUserInfo.mPassword, request.mUserInfo.mIconIndex)
            stat.executeUpdate(sql)

            // 发送结果
            regResult.mResultCode = ResultCode.REG_SUCCEEDED
            client.sendData(regResult)

            // 将该用户加入到在线列表中
            mOnlineUsers[request.mUserInfo.mUsername] = client
            stat.close()
        } catch (e: Exception) {
            // 该用户已存在
            regResult.mResultCode = ResultCode.USER_EXISTS
            try {
                client.sendData(regResult)
                stat?.close()
            } catch (e2: Exception) {
                e2.printStackTrace()
            }
        }
    }

    /**
     * 处理客户端发送来的数据。
     * @param pro       协议
     * @param client    客户端
     */
    @Synchronized
    fun onGetMessageFromClient(pro: ImProtocol, client: Client) {
        when (pro.mType) {
            ImProtocol.Type.ADD_FRIEND_REQUEST -> this.handleAddFriendRequest(pro as AddFriendRequest, client)
            ImProtocol.Type.ADD_FRIEND_RESULT -> this.handleAddFriendResult(pro as AddFriendResult)
            ImProtocol.Type.CHAT -> this.handleChat(pro as ChatInfo)
            ImProtocol.Type.LOGOFF -> this.handleLogoff(pro as LogoffRequest)
            ImProtocol.Type.LOGON -> this.handleLogon(pro as LogonRequest, client)
            ImProtocol.Type.REG -> this.handleReg(pro as RegRequest, client)
            else -> {}
        }
    }

    /**
     * 接受客户端连接的线程。
     */
    override fun run() {
        var clientSocket: Socket
        while (!super.isClosed()) {
            try {
                clientSocket = super.accept()
                Client(this, clientSocket).start()
            } catch (e: Exception) {
                break
            }
        }
    }

    /**
     * 开启服务。
     * @return 成功则返回 true，否则返回 false
     */
    fun start(): Boolean {
        // 绑定 IP 地址和端口号
        try {
            val inetAddress = InetAddress.getByName(ImProtocol.SERVER_IP)
            val socketAddress = InetSocketAddress(inetAddress, ImProtocol.SERVER_PORT)
            super.bind(socketAddress)
        } catch (e: Exception) {
            return false
        }

        // 开启接收数据的线程
        Thread(this).start()

        return true
    }
}
